<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
  <title>Google Code wiki syntax editor</title>
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
  
  <script type="text/javascript" src="lib/jsdifflib/difflib.js"></script>
  <script type="text/javascript" src="lib/jsdifflib/diffview.js"></script>
  <script type="text/javascript" src="lib/prettify/prettify.js"></script>
  <script type="text/javascript" src="lib/JSONMessage/JSONMessage.js.php"></script>
  <script type="text/javascript" src="lib/GoogleCodeWikiParser.js"></script>
  
  <link type="text/css" rel="stylesheet" href="lib/prettify/prettify.css" />
  <link type="text/css" rel="stylesheet" href="lib/jsdifflib/diffview.css" />
  <link type="text/css" rel="stylesheet" href="GoCoWi-previewer.css" />
  
  <style type="text/css">
    #ajaxNotification {
      position:absolute;
      top:0;
      left:0;
      border: 1px inset black;
      background-color:#773333;
      color:white;
      opacity:0.75;
    }
  select {
    font-size: 0.9em;
  }
  </style>
  
  <script type="text/javascript">
    var ThisApp = {
        gcwp:null,
        project:null,
        textarea:null,
        preview:null,
        projectInput:null,
        btnProjectLoad:null,
        pageList:null,
        hiddenDefaultWiki:null,
        homeProject:'wikiwym',
        viewMode:'preview',
        updateMode:'enter',
        updateTimerID:null,
        pageCache: {}
    };
    jQuery(document).ready(function() {
        jQuery.noConflict();
        ThisApp.gcwp = new GoogleCodeWikiParser();
        ThisApp.gcwp.options.outputSeparator = '\n';
        ThisApp.preview = jQuery('#wikicontent');
        ThisApp.textarea = jQuery('#textarea');
        ThisApp.hiddenDefaultWiki = jQuery('#DefaultWikiText');
        ThisApp.textarea.attr('value', ThisApp.hiddenDefaultWiki.text());
        ThisApp.projectInput = jQuery('#GoCoProjectName');
        ThisApp.btnProjectLoad  = jQuery('#LoadProjectButton');
        ThisApp.pageList = jQuery('#WikiPageList');
        ThisApp.textarea.keyup(function(event) {
            if( 'enter' == ThisApp.updateMode ) {
              if ((event.keyCode == '13'/*\r*/) || (event.keyCode=='9'/*tab*/)) {
                  updateView();
              }
            }
            else if( 'keypress' == ThisApp.updateMode ) {
              updateView();
            }
            else {
              // assume timer will get it.
            }
        });
        ThisApp.projectInput.change(function(event) {
            //if ((event.keyCode == '13')  || (event.keyCode=='9'/*tab*/)) {
            if( jQuery(this).attr('value') != ThisApp.project )
            {
                loadProjectList();
            }
            //}
        });
        ThisApp.cbEnableWarn = jQuery('#optionEnableWarnings');
        ThisApp.cbEnableWarn.click( function() {
            ThisApp.gcwp.options.disableWarnings = jQuery(this).is(':checked');
            updateView();
            //alert( ThisApp.gcwp.options.disableWarnings );
        });
        if(1)
        {
            var sel = jQuery('#SelectProject');
            //jQuery('option',sel).
            sel.change( function(){
                var val = jQuery('option:selected',this).attr('value');
                if( ! /^-/.test(val) )
                {
                    loadProjectList( val );
                }
            });
        }
        jQuery('input[name=viewMode]').click( function() {
          ThisApp.viewMode = jQuery(this).attr('value');
          updateView();
        });
        jQuery('#timerValue').change( function() {
          if( 'timer' == ThisApp.updateMode ) {
            var tv = jQuery('option:selected',jQuery(this)).attr('value');
            clearInterval( ThisApp.updateTimerID );
            ThisApp.updateTimerID = null;
            ThisApp.updateTimerID = setInterval( updateView, tv );
          }
        });
        jQuery('input[name=updateMode]').click( function() {
          var v = jQuery(this).attr('value');
          if( v == ThisApp.updateMode ) return;
          ThisApp.updateMode = v;
          if( 'timer' == v ) {
            jQuery('option:selected', jQuery('#timerValue') ).click();
          }
          else {
            clearInterval( ThisApp.updateTimerID );
            ThisApp.updateTimerID = null;
          }
        });
        if(1)
        {  // Add a visual notification when JSONMessage.Request messages are pending...
            var notif = jQuery('#ajaxNotification');
            notif.hide();
            notif.postCount = 0;
            notif.fadeSpeed = 'fast';
            notif.updateMessage = function(n)
            {
                this.postCount += n;
                this.text( this.postCount + ' pending JSONMessage.Request connection(s)...' );
                if( 1 == this.postCount ) this.fadeIn(this.fadeSpeed);
                else if( ! this.postCount ) this.fadeOut(this.fadeSpeed);
            };
            JSONMessage.Request.options.ajax.beforePost = function(req,opt)
            {
                notif.updateMessage(1);
            };
            JSONMessage.Request.options.ajax.afterPost = function(req,opt)
            {
                notif.updateMessage(-1);
            };
        }
        
        goHome();
    });
    function updateView()
    {
        /* src= jquery element whose height we need to get.
        dest = jquery element whose size needs to be set to src's size.
        */
        function obligatoryLayoutKludge(src,dest)
        {
          return;
          // obligatory layout kludge:
          var wid = jQuery('#layout').width() || jQuery('body').width() || 800;
          var ht =  src.height() || jQuery('body').height() || 800;
          if( ht < 64 ) ht = 64;
          wid = (wid-(wid/25))/2;
          dest.css({width:wid, height:ht});
          //ThisApp.preview.css({width:wid});
        }
        var labelElem = jQuery('<span></span>');//#viewModeName');
        try
        {
            var c = ThisApp.textarea.attr('value');
            if((!ThisApp.viewMode) || ('preview' === ThisApp.viewMode))
            {
                labelElem.text("Preview");
                c = ThisApp.gcwp.parse(c);
                if( ThisApp.pageName )
                {
                    c = '<h1>'+ThisApp.pageName+'</h1>'+c;
                }
                ThisApp.preview.html( c );
                //obligatoryLayoutKludge(ThisApp.preview, ThisApp.textarea);
                /**
                    Update links within the preview area:

                    - those starting with "http" or '#' are left as-is.

                    - Links where the href element exactly matches
                    one of the pages for the current project will
                    cause that page to load.
                */
                jQuery('a',ThisApp.preview)
                    .not('[href^="http"]')
                    .not('[href^=#]')
                    .click(function(){
                        var self = jQuery(this);
                        var hr = self.attr('href');
                        var linkto = jQuery('a',ThisApp.pageList).filter('[text='+hr+']');
                        if( linkto.length )
                        {
                            loadWikiPage( hr );
                            return false;
                        }
                        //else if( /^http/i, hr ) return true;
                        else
                        {
                            alert("This link is disabled by this application.");
                            return false;
                        }
                        // FIXME: we could point them back to the original source or load them.
                });
                prettyPrint()/* updates {{{code}}} blocks. */;
            }
            else if('diff' === ThisApp.viewMode)
            {
                labelElem.text("Diff");
                if( c )
                {
                  var orig = ThisApp.pageCache[ThisApp.pageName];
                  var base = difflib.stringAsLines(orig ? orig : c);
                  var newtxt = difflib.stringAsLines(c);
                  // create a SequenceMatcher instance that diffs the two sets of lines
                  var sm = new difflib.SequenceMatcher(base, newtxt);
                  // get the opcodes from the SequenceMatcher instance
                  // opcodes is a list of 3-tuples describing what changes should be made to the base text
                  //      in order to yield the new text
                  var opcodes = sm.get_opcodes();
                  var diffoutputdiv = ThisApp.preview;
                  diffoutputdiv.empty();
                  var contextSize = null;
                  // build the diff view and add it to the current DOM
                  diffoutputdiv.append(diffview.buildView({
                      baseTextLines:base,
                      newTextLines:newtxt,
                      opcodes:opcodes,
                      // set the display titles for each resource
                      baseTextName:"Server Version",
                      newTextName:"Your Version",
                      contextSize:contextSize,
                      viewType: 1 //$("inline").checked ? 1 : 0
                  }));
                }
                else {
                  ThisApp.preview.empty().text("Nothing to diff!");
                }
                //obligatoryLayoutKludge( jQuery(diffoutputdiv), ThisApp.textarea);
            }
            else if( 'html' === ThisApp.viewMode )
            {
                labelElem.text("HTML");
                ThisApp.preview.empty();
				
                if(c) c = ThisApp.gcwp.parse(c).replace(/&/g,'&amp;').replace(/</g, '&lt;');
				
                if( ThisApp.pageName ) {
                  ThisApp.preview.append( '<h1>'+ThisApp.pageName+'</h1>' );
                }
				
                var ta = jQuery("<pre class=\"prettyprint\"></pre>");
                ta.html( c );
                ThisApp.preview.append(ta);
                //ta.css( ThisApp.textarea.css() );// breaks in jQuery core
                //obligatoryLayoutKludge( ThisApp.textarea, ta );
                prettyPrint();
            }
            else
            {
              throw new Error("Internal error: unexpected view mode '"+ThisApp.viewMode+"'!");
            }
        }
        catch(e)
        {
            alert("Exception while parsing wiki code:\n"+e);
        }
    }

    // JSONMessage.Request.post() onResponse() handler.
    function processLoadedWikiPage(resp,req)
    {
        if( 0 != resp.resultCode() )
        {
            alert("Wiki page request returned error code #"+resp.resultCode()
                +"\n"+resp.resultText());
            return;
        }
        var p = resp.payload();
        ThisApp.pageName = p.page;
        ThisApp.pageCache[p.page] = p.content;
        ThisApp.textarea.attr('value',p.content);
        updateView();
    }

    function loadWikiPage(pageName)
    {
        ThisApp.pageName = null;
        ThisApp.textarea.text("Loading page "+ThisApp.project+'/'+pageName+' from Google Code subversion...');
        updateView();
        //alert("Clicked "+pg);
        var r = new JSONMessage.Request({type:'googlecode/getWikiPage',
                payload:{
                    project:ThisApp.project,
                    page:pageName
                }});
        r.post( {onResponse:processLoadedWikiPage} );
    }

    // JSONMessage.Request.post() onResponse() handler.
    function processLoadedProjectList(resp,req)
    {
        if( 0 != resp.resultCode() )
        {
            alert("Wiki list request returned error code #"+resp.resultCode()
                +"\n"+resp.resultText());
            return;
        }
        var p = resp.payload();
        var li = p.pages;
        if( !li || !li.length )
        {
            alert("Either the project has no wiki pages or fetching the list failed for an unknown reason.");
            return;
        }
        ThisApp.project = p.project;
        //alert("Got page list:\n"+JSON.stringify(li,undefined,2));

        function pageClick()
        {
            loadWikiPage( jQuery(this).text() );
            return false;
        }
        var jli = ThisApp.pageList;
        var link = "<a href='http://code.google.com/p/"+ThisApp.project+"' target='_new'>"
                +ThisApp.project+"</a>";
        jli.empty().append(jQuery('<b></b>').html(link+" wiki pages: "));
        var i, a;
        for( i = 0; i < li.length; ++i )
        {
            a = jQuery('<a href="#"></a>');
            a.text( li[i] ).click( pageClick );
            jli.append( a );
            if( i < (li.length-1) ) jli.append(' | ');
        }
    }
    function loadProjectList(proj)
    {
        if( ! proj ) {
          proj = ThisApp.projectInput.attr('value');
        }
        else {
          ThisApp.projectInput.attr('value',proj);
        }
        if( ! /^\S+$/.test(proj) )
        {
            alert("You must enter a Google Code project name first!");
            return;
        }
        ThisApp.pageList.empty().text('Loading wiki page list for '+proj+'...');
        var r = new JSONMessage.Request({type:'googlecode/getWikiList',
                    payload:{
                        project:proj
                    }});
        r.post( {onResponse:processLoadedProjectList} );
    }
    function goHome()
    {
        ThisApp.project = ThisApp.pageName = null;
        var val = ThisApp.hiddenDefaultWiki.html();
        ThisApp.textarea.attr('value', val );
        ThisApp.pageCache[ThisApp.pageName] = val; // necessary for diff to work for the local page.
        updateView();
        //if( ThisApp.project != ThisApp.homeProject )
        loadProjectList(ThisApp.homeProject);
    }

    function toggleControls()
    {
        jQuery('.togglableControl').toggle();
    }
  </script>
</head>
<body>
    <div style='text-align:center;padding:0px;margin:0px;'>
    <a href='#' onclick='toggleControls(); return false;'>Toggle controls on/off</a>
    </div>
    <div style='text-align:center;padding:0px;margin:0px;' class='togglableControl'>
    <h2><a href='http://code.google.com/p/wikiwym'>Wikiwym</a> Preview Application</h2>
    Enter a Google Code project name: <input type='text' id='GoCoProjectName' size='20' value='wikiwym'/>
    or:
		<select id='SelectProject' title='These projects were selected because i contribute to them or they have nice test pages :).'>
	    <option>--Select Project--</option>
	    <option>wikiwym</option>
	    <option>v8-juice</option>
	    <option>jsonmessage</option>
	    <option>whefs</option>
	    <option>qboard</option>
	    <option>v8</option>
	    <option>v8cgi</option>
	    <option>yapeal</option>
	    <option>cproxyv8</option>
	    <option>beup</option>
    </select>
    or: <button onclick="goHome()">Home</button>
	</div>
	
	<div id="WikiPageList" class='togglableControl'>
		When a project is selected, its wiki pages will be listed here.
    </div>
	
  <table class="layout" style='margin:0.5em;width:98%;'>
  <tr class='togglableControl'>
<!--  	<td><strong>Google Code-style Wiki Markup</strong></td>-->
    <td colspan='2'> <!--align='right' valign='bottom'-->
      <h2 style='float:left; clear:none;'>Google Code-style Wiki Markup:</h2>
      <div style='float:right; clear:none;'>
        <fieldset title="Switch view modes.">
          <legend>View Modes</legend> 
          <label><input type="radio" name="viewMode" value="preview" checked="checked"/>Preview</label>
          <label><input type="radio" name="viewMode" value="html" />HTML</label>
          <label><input type="radio" name="viewMode" value="diff" />Diff</label>
        </fieldset>
        <fieldset title="Change when the preview is updated.">
          <legend>Update preview on...</legend>
          <label><input type="radio" name="updateMode" value="enter" checked="checked"/>ENTER</label>
          <label><input type="radio" name="updateMode" value="keypress" />Keypress</label>
          <label><input type="radio" name="updateMode" value="timer" />Timer:
              <select id='timerValue'>
              <option value='3000'>3s</option>
              <option value='5000'>5s</option>
              <option value='10000' selected>10s</option>
              <option value='30000'>30s</option>
              <option value='60000'>60s</option>
              </select>
          </label>
        </fieldset>
        <fieldset title="Note that warnings may be caused by some malformed wiki code or bugs in this parser">
          <legend>Options</legend> 
          <label><input type="checkbox" id="optionEnableWarnings" /> Disable warnings?</label>
        </fieldset>
      </div>
    </td>
  </tr>
  <tr>
    <td class="editor">
      <textarea id="textarea" wrap="soft">
If you see this text then your browser cannot handle the JavaScript needed
to run this application. Sorry. Try <a href='http://www.google.com/chrome'>Google Chrome</a>.
</textarea>
    </td>
    <td class="preview">
    	<div id="wikicontent"></div>
		</td>
  </tr>
</table>
<!--
<wiki:toc max_depth=3></wiki:toc>
-->
<div id='DefaultWikiText' style='visibility:hidden'>
=[http://code.google.com/p/wikiwym Wikiwym] demonstration!=

This page demonstrates wikiwym, a !JavaScript wiki syntax parser which supports a large subset of the Google Code wiki syntax.

=Using this Application=

In the text field at the top of the screen you can enter the name of a project hosted at
[http://code.google.com Google Code] to load the list of wiki pages for that project
(taken from their SVN repository). Then click on a page name to preview it here.

You can edit the wiki markup. Changes made will be reflected in the preview/diff/HTML
views on the right-hand side. (_When_ the updates happen depend on the Update settings,
which can be changed via the menu at the top.)

=About this Application=
This app was written by [http://wanderinghorse.net/home/stephan/ Stephan Beal] with help from:

  * [http://code.google.com/p/wikiwym Wikiwym] for the wiki-to-HTML conversion.
  * [http://code.google.com/u/fabien.menager/ Fabien M&#0233;nager], co-developer of Wikiwym, provided the initial inspiration and made this UI pretty.
  * The [http://code.google.com/p/jsonmessage JSONMessage] framework collects the wiki pages from google.
  * [http://www.jquery.com jQuery] simplifies the DOM manipulation and UI interaction.
  * [http://code.google.com/p/google-code-prettify/ google-code-prettify] does the syntax highlighting in wiki source code blocks and the HTML preview.
  * [http://snowtide.com/jsdifflib Chas Emerick] wrote the "diff" view code.
  * [http://getfirebug.com/ Firebug] (in [http://www.getfirefox.com Firefox]) was often helpful, but most testing took place in [http://www.google.com/chrome Google Chrome]. (Running on Linux, of course.)
  * [http://kdewebdev.org Quanta/KDEWebDev] as the dev environment (because Emacs can't indent !JavaScript worth a damn).
  * A bacon and egg sandwich to curb the hunger in the middle of the hacking session.
  * [http://www.bobross.com Bob Ross]. He's The Man.

=Potential TODOs=

  * Install a client-side cache for requested pages and project wiki lists, with a timeout/expiry time on cache entries (handled via a `setInterval()` loop). The RPC/messaging framework gives us a hook-in place to do this with, but it needs to be coded.
  * With the cache in place we can allow swapping between multiple pages, keeping any hand-edited changes between swaps. i.e. one could work on multiple pages at a time.
  * A history list of pages/projects visits this session, to allow faster swapping between them.
  * Ability to load a specific version of a wiki page.

All of that is simple enough to do, but as of this moment i've been awake 25 hours and hacking on this code the last 19 of them, and i'm
too tired to deal with new "nice-to-have" features.

=Brought to You By...=

Stephan Beal: [http://wanderinghorse.net/home/stephan/ http://wanderinghorse.net/home/stephan/stephan_beal_sylt_072006_small.jpg]
and
Fabian  M&#0233;nager: [http://phenxdesign.net/  http://fossil.wanderinghorse.net/demos/wikiwym/fabien-menager.jpg]
</div>

<span id='ajaxNotification'></span>
<!--
  * *bold* _emphasized_ *_bold emphasized ^SuperScript^!_*.
  * item 2
  * item 3

|| *a* || *b* || *c*||
|| a2 || b2 || c2||
|| a3 || b3 || c3||

  # ~~item~~
  # _~~item~~_
  * item

Intentional errors:

  * *unterminated bold
  * unterminated _*italics*
  * unterminated ^sup
  * mis-terminated ,,sub,
  * unterminated ,,sub

(If those are NOT _all_ marked with errors, something is broken.)
-->
</body>
</html>
